/*
 * Miccsnookersim, a snooker simulation with AI
 * Copyright (C) 2009 Enno Ruijters, Frans van den Heuvel, Yannick Thimister,
 * Ali B. Ozmen, Esther Verhoef, Achim Leydecker
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.awt.geom.Point2D;

/**
 * Class representing a 3-dimensional vector of real elements.
 * All methods with side-effects are indicated by a trailing underscore.
 */

/* All methods of this class that do not end their names with an underscore
 * are fully thread-safe with respect to each other.
 */
public class Vector
{
	private double[] components;

	private Vector()
	{
		components = new double[3];
	}

	public void print(boolean newLine)
	{
		System.out.format("%f %f %f%s", components[0], components[1],
		                  components[2], newLine ? "\n" : "");
	}

	public Vector(double x, double y, double z)
	{
		components = new double[3];
		components[0] = x;
		components[1] = y;
		components[2] = z;
	}

	/**
	 * Get the x, y and z components of the vector
	 */
	public double[] getElements()
	{
		return (double[]) components.clone();
	}

	/**
	 * Clones the vector
	 */
	public Vector clone()
	{
		Vector ret = new Vector();
		ret.components = new double[components.length];
		for(int i = 0; i < this.components.length; i ++)
		{
			ret.components[i] = this.components[i];
		}
		return ret;
	}

	/**
	 * Calculates the dot product of two vectors.
	 */
	public static double dotProduct(Vector a, Vector b)
	{
		return a.components[0] * b.components[0]
		       + a.components[1] * b.components[1]
		       + a.components[2] * b.components[2];
	}

	/**
	 * Subtract two vectors.
	 */
	public static Vector subtract(Vector a, Vector b)
	{
		Vector ret = new Vector();
		ret.components[0] = a.components[0] - b.components[0];
		ret.components[1] = a.components[1] - b.components[1];
		ret.components[2] = a.components[2] - b.components[2];
		return ret;
	}

	/**
	 * Subtracts a vector from this one (destructively).
	 */
	public Vector sub_(Vector v)
	{
		components[0] -= v.components[0];
		components[1] -= v.components[1];
		components[2] -= v.components[2];
		return this;
	}

	/**
	 * Adds two vectors
	 */
	public static Vector add(Vector a, Vector b)
	{
		Vector ret = new Vector();
		ret.components[0] = a.components[0] + b.components[0];
		ret.components[1] = a.components[1] + b.components[1];
		ret.components[2] = a.components[2] + b.components[2];
		return ret;
	}

	/**
	 * Add a vector to this vector (destructively)
	 */
	public Vector add_(Vector v)
	{
		components[0] += v.components[0];
		components[1] += v.components[1];
		components[2] += v.components[2];
		return this;
	}

	/**
	 * Normalizes the vector to have an absolute magnitude of 1
	 * (destructively).
	 */
	public Vector normalize_()
	{
		double length = magnitude();
		if (length == 0)
			return this;

		components[0] /= length;
		components[1] /= length;
		components[2] /= length;
		return this;
	}

	/**
	 * Scales a vector to have a specific absolute magnitude (destrucively).
	 */
	public Vector scaleTo_(double magnitude)
	{
		final double length = magnitude();
		if (length == 0)
			return this;
		final double factor = length / magnitude;

		components[0] /= factor;
		components[1] /= factor;
		components[2] /= factor;
		return this;
	}

	/**
	 * Calculates the absolute magnitude of the vector.
	 */
	public double magnitude()
	{
		return Math.sqrt(components[0] * components[0] +
		                 components[1] * components[1] +
		                 components[2] * components[2]);
	}

	/**
	 * Scales a vector by a real factor (destructively).
	 */
	public Vector scale_(double factor)
	{
		components[0] *= factor;
		components[1] *= factor;
		components[2] *= factor;
		return this;
	}

	/**
	 * Finds the angle between two angles in the x-y plane.
	 * @return the angle in radians
	 */
	public static double angle(Vector v, Vector ref)
	{
		double angle = Math.atan2(v.components[1], v.components[0]);
		double refAngle = Math.atan2(ref.components[1], ref.components[0]);
		return angle - refAngle;
	}

	/** Finds the angle between this vector and the x-y plane.
	 *  @return the angle in radians
	 */
	public double Zangle()
	{
		final double deltaXY =
		    Math.sqrt(components[0] * components[0]
		              + components[1] * components[1]);
		return Math.atan2(components[2], deltaXY);
	}



	/**
	 * Rotates the vector a certain angle around the z-axis.
	 */
	public Vector rotate(double angle)
	{
		Vector ret = new Vector();
		ret.components[0] = components[0] * Math.cos(angle)
		                    - components[1] * Math.sin(angle);
		ret.components[1] = components[0] * Math.sin(angle)
		                    + components[1] * Math.cos(angle);
		return ret;
	}

	/**
	 * Rotates the vector a certain angle around the z-axis (destructively).
	 */
	public Vector rotate_(double angle)
	{
		double oldComponent0 = components[0];
		components[0] = components[0] * Math.cos(angle)
		                - components[1] * Math.sin(angle);
		components[1] = oldComponent0 * Math.sin(angle)
		                + components[1] * Math.cos(angle);
		return this;
	}

	/**
	 * Rotates the vector a certain angle around the x-axis (destructively).
	 */
	public Vector rotateAroundX_(double angle)
	{
		double oldComponent1 = components[1];
		components[1] = components[1] * Math.cos(angle)
		                - components[2] * Math.sin(angle);
		components[2] = oldComponent1 * Math.sin(angle)
		                + components[2] * Math.cos(angle);
		return this;
	}

	/**
	 * Calculates the cartesian distance between two vectors.
	 */
	public double distanceFrom(Vector other)
	{
		double squareSum = 0;
		for (int i = 0; i < components.length; i++)
			squareSum += (components[i] - other.components[i])
			             * (components[i] - other.components[i]);
		return Math.sqrt(squareSum);
	}

	/**
	 * Mirror the vector about the Y-axis (destructively).
	 */
	public Vector mirrorY_()
	{
		components[1] = -components[1];
		return this;
	}

	/**
	 * Multiply the components of this vector by the components of another
	 * vector (destructively).
	 */
	public Vector mul_(Vector v)
	{
		components[0] *= v.components[0];
		components[1] *= v.components[1];
		components[2] *= v.components[2];
		return this;
	}

	public Point2D toPoint()
	{
		return new Point2D.Double(components[0], components[1]);
	}
}
